#include <linux/bio.h>
#include <linux/init.h> /* Needed for the macros */
#include <linux/kernel.h> /* Needed for pr_info() */
#include <linux/module.h> /* Needed by all modules */
#include <linux/sched.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <uapi/linux/hdreg.h>

#define HEADS 4
#define SECTORS 16
#define CYLINDERS 256
#define SECTORS_SIZE 512
#define SECTORS_TOTAL (HEADS * SECTORS * CYLINDERS)
 //8M
#define VDISK_SZIE (SECTORS_TOTAL * SECTORS_SIZE)

#define   DEVICE_NAME  "mydisk"

/**
 *
 * https://linux-kernel-labs.github.io/refs/heads/master/labs/block_device_drivers.html
 *
 * https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch16.html
 *
 * https://bootlin.com/doc/legacy/block-drivers/block_drivers.pdf
 *
 *
 * https://kernel.dk/blk-mq.pdf
 *
 * A block is a fixed-size chunk of data, the size being determined by the kernel.
 * Blocks are often 4096 bytes, but that value can vary depending
 * on the architecture and the exact filesystem being used
 *
 *
 * sudo  fdisk /dev/mydisk
 * then n  p  w
 * sudo mkfs.ext2 /dev/mydisk1
 * sudo  mount /dev/mydisk1  /tmp
 * sudo  vi  to  write to  txt  file.
 * then use  cat  to  verify.
 *
 *
 *
 */

static int major;

struct vdsk_dev  {
	unsigned long size;
	u8 *data;
	spinlock_t lock ;
	struct gendisk *mydisk;
	struct request_queue *queue;
};

struct vdsk_dev *vdsk;

int myopen(struct block_device *dev, fmode_t mode) {
	return 0;
}

void myrelease(struct gendisk *disk, fmode_t mode) {
}

int mygetgeo(struct block_device *dev, struct hd_geometry *geo) {
	pr_err("mygetgeo");
	geo->heads = HEADS;
	geo->sectors = SECTORS;
	geo->cylinders = CYLINDERS;
	geo->start = 0;
	return 0;
}

/* This function also called from IRQ context */
static void my_request(struct request_queue *q)
{
	struct vdsk_dev  *mydev = q->queuedata;
	struct request *req;
	u32 sect_num, sect_cnt;
	struct bio *bio;
	struct bio_vec bvec;
	struct bvec_iter iter;
	char *buffer;
	unsigned long offset;
	unsigned long nbytes;
	int segment = 0;
	int  cbio =0;
	req = blk_fetch_request(q);
	pr_err("my_request start");
	while (req != NULL) {
		sect_num = blk_rq_pos(req);
		/* deal whole segments */
		sect_cnt = blk_rq_sectors(req);
		__rq_for_each_bio(bio,req){
			segment = 0;
			cbio++;
			pr_err("now process bio: %d",cbio);
			bio_for_each_segment(bvec,bio,iter){
				segment++;
				pr_err("now process segment: %d",segment);
				buffer =  __bio_kmap_atomic(bio,iter);
				offset =iter.bi_sector * SECTORS_SIZE ;
				nbytes = bvec.bv_len ;

				if (bio_data_dir(bio) == WRITE) {
					pr_err("my_request WRITE");
					memcpy(mydev->data + offset, buffer, nbytes);
				} else {
					pr_err("my_request read");
					memcpy(buffer, mydev->data + offset, nbytes);
				}
				__bio_kunmap_atomic(bio);
			}
		}

		if(!__blk_end_request_cur(req,0)){
			req = blk_fetch_request(q);
}
	}
	 pr_err("my_request end");
}

static const struct block_device_operations my_bd_ops = { .owner = THIS_MODULE,
		.open = myopen, .release = myrelease, .getgeo = mygetgeo, };

static __init int my_init(void) {
	  int ret;
	  ret = -EBUSY;
	  major = register_blkdev(0,DEVICE_NAME);
	  if(major<=0){
	 pr_err("register_blkdev fail");
	 goto err;
	  }

	pr_err("SECTORS_TOTAL %d",SECTORS_TOTAL);

	vdsk = kzalloc(sizeof(struct vdsk_dev),GFP_KERNEL);
	vdsk->size = VDISK_SZIE;
	vdsk->data = vmalloc(VDISK_SZIE);
    pr_err("00000000000000000");

    ret = -ENOMEM;
    vdsk->mydisk = alloc_disk(4);
    pr_err("alloc_disk");
    if (!vdsk->mydisk)
   	goto out_disk;
       vdsk->mydisk->major = major;
       vdsk->mydisk->first_minor = 0;
       vdsk->mydisk->fops =  &my_bd_ops;

    spin_lock_init(&vdsk->lock);
    vdsk->queue = blk_init_queue(my_request,&vdsk->lock);
    pr_err("blk_init_queue");

    if (!vdsk->queue)
   	goto out_queue;

    blk_queue_logical_block_size(vdsk->queue, SECTORS_SIZE);
    vdsk->queue->queuedata = vdsk;

    vdsk->mydisk->queue = vdsk->queue;
    vdsk->mydisk->private_data = vdsk ;
	sprintf(vdsk->mydisk->disk_name,"mydisk");

	set_capacity(vdsk->mydisk,SECTORS_TOTAL);
	add_disk(vdsk->mydisk);
	pr_err("add_disk");
	pr_err("my_init end ");
	return 0;

out_queue:
	pr_err("out_queue");
	put_disk(vdsk->mydisk);

out_disk:
	pr_err("out_disk");
	unregister_blkdev(major, DEVICE_NAME);

err:
	return ret;
}

static void __exit my_exit(void) {
	del_gendisk(vdsk->mydisk);
	blk_cleanup_queue(vdsk->queue);
	put_disk(vdsk->mydisk);
	unregister_blkdev(major,DEVICE_NAME);
	vfree(vdsk->data);
	kfree(vdsk);
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andy");
MODULE_DESCRIPTION("andy one-key driver");
MODULE_ALIAS("one-key");
77
